using System;
using System.Text;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using System.Reflection;
using UnityEditorInternal;
/// <summary>
/// 功能和 [UnityEngine.Scripting.Preserve] 类似
/// </summary>
namespace Framework
{
    public enum LinkerType
    {
        //按类型添加
        Type,
        //按命名空间添加
        Namespace,
        All,
    }
    [System.Serializable]
    public class LinkerItem
    {
        [Header("程序集")]
        public string Assembly = "Assembly-CSharp";
        [Header("类型")]
        public LinkerType Type = LinkerType.All;
        [Header("列表")]
        public string[] Items;
    }
    internal class AssemblyDefinition
    {
        public string name;
    }
    public class Linker : ScriptableObject
    {
        private const string ConfigFile = FrameworkSetting.SETTING_FOLDER + "LinkerConfig.asset";
        [Header("程序集列表")]
        public List<AssemblyDefinitionAsset> Assemblies;
        [Header(" ----- 生成link.xml ----- ")]
        [Header("保存目录")]
        public UnityEngine.Object Output;
        [Header("代码无引用时的配制列表")]
        public LinkerItem[] Items;
        private static Linker instance = null;

        public static Linker Instance
        {
            get
            {
                if (instance != null)
                {
                    return instance;
                }
                if (File.Exists(ConfigFile))
                {
                    instance = AssetDatabase.LoadAssetAtPath<Linker>(ConfigFile);
                }
                else
                {
                    FrameworkSetting.CreateFolder();
                    instance = CreateInstance<Linker>();
                    AssetDatabase.CreateAsset(instance, ConfigFile);
                }
                return instance;
            }
        }
        public void Execute(bool select = false)
        {
            if (Output == null)
            {
                if (EditorUtility.DisplayDialog("警告", "需要设置目录设置到Output变量上", "确定"))
                {
                    EditorGUIUtility.PingObject(instance);
                    UnityEditor.Selection.activeObject = instance;
                }
                return;
            }
            var path = AssetDatabase.GetAssetPath(Output);
            if (string.IsNullOrEmpty(path)) return;
            var linkPath = Path.Combine(path, "link.xml");
            Dictionary<string, bool> map = new Dictionary<string, bool>();
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Clear();

            stringBuilder.AppendLine("<linker>");
            if (Items.Length > 0)
            {

                foreach (var item in Items)
                {
                    var assembly = Utils.Assembly.GetAssembly(item.Assembly);
                    if (assembly == null)
                    {
                        Debug.LogErrorFormat("not find Assembly {0}", item.Assembly);
                        continue;
                    }
                    if (!map.ContainsKey(assembly.FullName))
                    {
                        map.Add(assembly.FullName, true);
                    }
                    if (item.Type == LinkerType.All)
                    {
                        stringBuilder.AppendLine(string.Format("<assembly fullname=\"{0}\" preserve=\"all\">", assembly.FullName));
                    }
                    else
                    {
                        stringBuilder.AppendLine(string.Format("<assembly fullname=\"{0}\">", assembly.FullName));
                        foreach (var name in item.Items)
                        {
                            if (string.IsNullOrEmpty(name)) continue;
                            if (item.Type == LinkerType.Namespace)
                            {
                                // <namespace fullname="UnityEngine.ProBuilder" preserve="all"/>
                                stringBuilder.AppendLine(string.Format("<namespace fullname=\"{0}\" preserve=\"all\"/>", name));
                            }
                            else if (item.Type == LinkerType.Type)
                            {
                                // <type fullname="UnityEngine.ProBuilder" preserve="all"/>
                                var type = Utils.Assembly.GetType(name, assembly);
                                if (type == null)
                                {
                                    Debug.LogErrorFormat("not find type {0}", name);
                                    continue;
                                }
                                var l = new List<Type>();
                                Utils.Assembly.ForeachType(type, assembly, t =>
                                {
                                    if (!t.IsAbstract)
                                    {
                                        l.Add(t);
                                    }
                                });
                                if (l.Count < 1)
                                {
                                    Debug.LogErrorFormat("not find sub type {0} in {1}", name, item.Assembly);
                                }
                                foreach (var t in l)
                                {
                                    stringBuilder.AppendLine(string.Format("<type fullname=\"{0}\" preserve=\"all\"/>", t.FullName));
                                }
                            }
                        }

                    }
                    stringBuilder.AppendLine("</assembly>");
                }
            }
            var assNameList = new List<string>();
            foreach (var item in Assemblies)
            {
                var def = Utils.Json.ToObject<AssemblyDefinition>(item.text);
                assNameList.Add(def.name);
                var assembly = Utils.Assembly.GetAssembly(def.name);
                if (!map.ContainsKey(assembly.FullName))
                {
                    stringBuilder.AppendLine(string.Format("<assembly fullname=\"{0}\" preserve=\"all\"></assembly>", assembly.FullName));
                    map.Add(assembly.FullName, true);
                }
            }
            var assList = System.AppDomain.CurrentDomain.GetAssemblies();
            foreach (var item in assList)
            {
                var n = item.GetName().Name;
                if (assNameList.Contains(n))
                {
                    var names = item.GetReferencedAssemblies();
                    foreach (var refass in names)
                    {
                        if (!map.ContainsKey(refass.FullName))
                        {
                            stringBuilder.AppendLine(string.Format("<assembly fullname=\"{0}\" preserve=\"all\"></assembly>", refass.FullName));
                            map.Add(refass.FullName, true);
                        }
                    }
                }
            }
            stringBuilder.AppendLine("</linker>");
            try
            {
                if (File.Exists(linkPath))
                {
                    File.Delete(linkPath);
                }
                File.WriteAllText(linkPath, stringBuilder.ToString());
                stringBuilder.Clear();
            }
            catch (System.Exception)
            {
                Debug.LogErrorFormat("create link.xml fail {0}", linkPath);
            }

            AssetDatabase.Refresh();
            if (select)
            {
                var f = AssetDatabase.LoadMainAssetAtPath(Linker.ConfigFile);
                if (f == null) return;
                EditorGUIUtility.PingObject(f);
            }
        }
    }
}